package com.gitee.wsl.struct.grammar.parser.result

import com.gitee.wsl.struct.grammar.parser.token.Token
import com.gitee.wsl.struct.grammar.parser.token.TokenMatch
import com.gitee.wsl.struct.grammar.util.replaceNonPrintable
import com.gitee.wsl.struct.grammar.util.toPrintableString

private const val arrowDown = "\u2193"
private const val arrowUp = "\u2191"

/**
 * Result of failed parsing.
 */
abstract class ParseError : ParseResult<Nothing>() {
    /**
     * Offset in the input at which the parsing error occurred
     */
    abstract val offset: Int

    open val contextProvider: ParseErrorContextProvider? get() = null

    abstract fun describe(): String

    override fun toString(): String = describe()

    protected fun format(message: String, messageAtOffset: String): String = buildString {
        append(message)
        contextProvider?.getParseErrorContext(offset)?.run {
            appendLine()
            append(" ".repeat(lookBehind)).append(messageAtOffset)
            appendLine()
            append(" ".repeat(lookBehind)).append("$arrowDown offset=$offset (or after ignored tokens)")
            appendLine()
            appendLine(replaceNonPrintable(inputSection))
            if (previousTokenMatch != null) {
                append(arrowUp.repeat(previousTokenMatch.length.coerceAtLeast(1)))
                append(" Previous token: ${previousTokenMatch.token} at offset=${previousTokenMatch.offset}")
                appendLine()
            }
        }
    }
}

data class UnmatchedToken(
    val expected: Token,
    override val offset: Int,
    override val contextProvider: ParseErrorContextProvider? = null
) : ParseError() {

    override fun toString(): String = describe()

    override fun describe(): String = format(
        message = "Unmatched token at offset=$offset, when expected: ${expected.toPrintableString()}",
        messageAtOffset = "Expected token: ${expected.toPrintableString()}"
    )
}

data class MismatchedToken(
    val expected: Token,
    val found: TokenMatch,
    override val contextProvider: ParseErrorContextProvider? = null,
) : ParseError() {
    override val offset: Int get() = found.offset
    override fun toString(): String = describe()
    override fun describe(): String = format(
        message = "Mismatched token at offset=$offset, when expected: ${expected.toPrintableString()}, got: ${found.token.toPrintableString()}",
        messageAtOffset = "Expected token: ${expected.toPrintableString()} at offset=$offset, got: ${found.token.toPrintableString()}"
    )
}

data class NoMatchingToken(
    override val offset: Int,
) : ParseError() {

    override fun toString(): String = describe()
    override fun describe(): String = format(
        message = "No matching token at offset=$offset",
        messageAtOffset = "No matching token"
    )
}

data class NoViableAlternative(
    override val offset: Int,
) : ParseError() {
    override fun toString(): String = describe()
    override fun describe(): String = format(
        message = "None of the alternatives succeeded at offset=$offset",
        messageAtOffset = "None of the alternatives succeeded"
    )
}

data class NotEnoughRepetition(
    override val offset: Int,
    val expectedAtLeast: Int,
    val actualCount: Int
) : ParseError() {
    override fun toString(): String = describe()
    override fun describe(): String = "Expected at least $expectedAtLeast, found $actualCount"
}